﻿using System;
using System.Globalization;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Data;
using TidyBlog.Data.Mapping;

namespace TidyBlog.Data
{
    /// <summary>
    /// 简单的ORM辅助类
    /// </summary>
    /// <typeparam name="T">实体类</typeparam>
    public abstract class SimpleORM<T> where T : new()
    {
        /// <summary>
        /// 实体类型
        /// </summary>
        protected static readonly Type EntityType = typeof(T);

        /// <summary>
        /// 表属性
        /// </summary>
        protected static TableAttribute TableAttr;
        /// <summary>
        /// 列属性集合
        /// </summary>
        protected static Dictionary<string, ColumnAttribute> ColAttrs;

        /// <summary>
        /// SQL命令文本
        /// </summary>
        protected static Dictionary<string, string> CmdTexts;
        /// <summary>
        /// SQL命令用到的参数名
        /// </summary>
        protected static Dictionary<string, string[]> CmdColNames;

        /// <summary>
        /// 排序列
        /// </summary>
        protected static SortColumn[] _SortColumns;
        /// <summary>
        /// 列表列
        /// </summary>
        protected static string[] _ListColumns;


        /// <summary>
        /// 获取表名
        /// </summary>
        public static string TableName
        {
            get { return TableAttr.Name; }
        }
        /// <summary>
        /// 获取排序列
        /// </summary>
        public static SortColumn[] SortColumns
        {
            get { return _SortColumns == null ? null : (SortColumn[])_SortColumns.Clone(); }
        }
        /// <summary>
        /// 获取列表列
        /// </summary>
        public static string[] ListColumns
        {
            get { return _ListColumns == null ? null : (string[])_ListColumns.Clone(); }
        }


        static SimpleORM()
        {
            GetTableAttribute();
            GetColumnAttributes();
        }


        /// <summary>
        /// 获取映射的表属性
        /// </summary>
        static void GetTableAttribute()
        {
            object[] attrs = EntityType.GetCustomAttributes(typeof(TableAttribute), true);
            if (attrs.Length > 0)
            {
                var tableAttr = (TableAttribute)attrs[0];
                if (String.IsNullOrEmpty(tableAttr.Name))
                {
                    // 表名默认为该实体的类名
                    tableAttr.Name = EntityType.Name;
                }

                TableAttr = tableAttr;
            }
            else
            {
                throw new Exception("实体类 " + EntityType.Name + " 未映射到任何数据表");
            }
        }

        /// <summary>
        /// 获取映射的列属性集合
        /// </summary>
        static void GetColumnAttributes()
        {
            ColAttrs = new Dictionary<string, ColumnAttribute>();
            Type attrType = typeof(ColumnAttribute);

            // 存放用于生成SQL的参数
            List<ColumnValuePair> insertCols = new List<ColumnValuePair>(),
                updateCols = new List<ColumnValuePair>();
            List<WhereCondition> primaryCols = new List<WhereCondition>();
            List<string> listCols = new List<string>();
            SortColumn[] sortCols = new SortColumn[255];

            // 存放每个命令用到的列名
            List<string> insertColNames = new List<string>(),
                updateColNames = new List<string>(),
                deleteColNames = new List<string>(),
                selectColNames = new List<string>();

            // 列表命令是否列出所有字段
            bool isListAllCols = true;

            // 遍历实体类每个属性
            foreach (PropertyInfo p in EntityType.GetProperties())
            {
                object[] attrs = p.GetCustomAttributes(attrType, true);

                // 不包含数据列特性，忽略之
                if (attrs.Length == 0)
                {
                    continue;
                }

                var colAttr = (ColumnAttribute)attrs[0];
                // 记录实体类对应的属性名
                colAttr.EntityPropertyName = p.Name;

                if (String.IsNullOrEmpty(colAttr.Name))
                {
                    // 列名默认与属性名相同
                    colAttr.Name = p.Name;
                }
                if (colAttr.DataType == default(DbType))
                {
                    // 默认数据类型映射
                    colAttr.DataType = DbHelper.Provider.GetDbType(p.PropertyType);
                }
                ColAttrs.Add(colAttr.Name, colAttr);

                // 主键
                if (colAttr.IsPrimaryKey)
                {
                    primaryCols.Add(new WhereCondition(colAttr));

                    // READ、UPDATE、DELETE命令均需以此列为索引
                    selectColNames.Add(colAttr.Name);
                    updateColNames.Add(colAttr.Name);
                    deleteColNames.Add(colAttr.Name);
                }
                // 插入命令用到的列
                if (!TableAttr.IsIgnoreInsert && !colAttr.IsDbGenerated)
                {
                    insertCols.Add(new ColumnValuePair(colAttr));

                    insertColNames.Add(colAttr.Name);
                }
                // 更新命令用到的列
                if (!TableAttr.IsIgnoreUpdate && !colAttr.IsUpdateIgnore)
                {
                    updateCols.Add(new ColumnValuePair(colAttr));

                    updateColNames.Add(colAttr.Name);
                }
                // 列表命令用到的列
                if (!TableAttr.IsIgnoreList)
                {
                    if (colAttr.IsListIgnore)
                    {
                        isListAllCols = false; 
                    }
                    else
                    {
                        listCols.Add(colAttr.Name);
                    }
                }

                // 排序列
                if (colAttr.OrderBy > 0)
                {
                    sortCols[colAttr.OrderBy - 1] = new SortColumn(colAttr);
                }
            }

            CmdTexts = new Dictionary<string, string>();
            CmdColNames = new Dictionary<string, string[]>();

            if (sortCols.Length > 0)
            {
                List<SortColumn> trimedSortCols = new List<SortColumn>();
                foreach (var sortCol in sortCols)
                {
                    if (sortCol != null)
                    {
                        trimedSortCols.Add(sortCol);
                    }
                }
                if (trimedSortCols.Count > 0)
                {
                    _SortColumns = trimedSortCols.ToArray();
                }
            }

            if (!TableAttr.IsIgnoreRead)
            {
                CmdTexts.Add("READ", (new SelectCommand(TableAttr.Name))
                    .Select("*")
                    .Top(1)
                    .Where(primaryCols)
                    .ToString()
                );

                if (selectColNames.Count > 0)
                {
                    CmdColNames.Add("READ", selectColNames.ToArray());
                }
            }

            if (listCols.Count > 0)
            {
                if (isListAllCols)
                {
                    listCols.Clear();
                    listCols.Add("*");
                }
                _ListColumns = listCols.ToArray();

                CmdTexts.Add("LIST", (new SelectCommand(TableAttr.Name))
                    .Select(_ListColumns)
                    .OrderBy(_SortColumns)
                    .ToString()
                );
            }

            if (insertCols.Count > 0)
            {
                CmdTexts.Add("INSERT", (new InsertCommand(TableAttr.Name))
                    .Values(insertCols)
                    .SelectLastId(TableAttr.IsSelectLastIdAfterInsert)
                    .ToString()
                );

                if (insertColNames.Count > 0)
                {
                    CmdColNames.Add("INSERT", insertColNames.ToArray());
                }
            }

            if (updateCols.Count > 0)
            {
                CmdTexts.Add("UPDATE", (new UpdateCommand(TableAttr.Name))
                    .Set(updateCols)
                    .Where(primaryCols)
                    .ToString()
                );

                if (updateColNames.Count > 0)
                {
                    CmdColNames.Add("UPDATE", updateColNames.ToArray());
                }
            }

            if (!TableAttr.IsIgnoreDelete)
            {
                CmdTexts.Add("DELETE", (new DeleteCommand(TableAttr.Name))
                    .Where(primaryCols)
                    .ToString()
                );

                if (deleteColNames.Count > 0)
                {
                    CmdColNames.Add("DELETE", deleteColNames.ToArray());
                }
            }
        }


        /// <summary>
        /// 添加命令参数
        /// </summary>
        /// <param name="cmd">命令对象</param>
        /// <param name="colName">列名</param>
        /// <param name="value">参数值</param>
        /// <returns>命令对象</returns>
        public static IDbCommand AttachParam(IDbCommand cmd, string colName, object value)
        {
            return AttachParam(cmd, colName, colName, value);
        }

        /// <summary>
        /// 添加命令参数
        /// </summary>
        /// <param name="cmd">命令对象</param>
        /// <param name="colName">列名</param>
        /// <param name="paramName">参数名</param>
        /// <param name="value">参数值</param>
        /// <returns>命令对象</returns>
        public static IDbCommand AttachParam(IDbCommand cmd, string colName, string paramName, object value)
        {
            var colAttr = ColAttrs[colName];

            DbHelper.AttachParams(cmd, DbHelper.CreateParam(
                paramName,
                value,
                colAttr.DataType,
                colAttr.Length
            ));

            return cmd;
        }

        /// <summary>
        /// 添加命令参数
        /// </summary>
        /// <param name="cmd">命令对象</param>
        /// <param name="colNames">列名</param>
        /// <param name="entity">实体类</param>
        /// <returns>命令对象</returns>
        public static IDbCommand AttachParam(IDbCommand cmd, string[] colNames, T entity)
        {
            foreach (var colName in colNames)
            {
                AttachParam(cmd, colName,
                    EntityType.GetProperty(ColAttrs[colName].EntityPropertyName).GetValue(entity, null));
            }

            return cmd;
        }

        /// <summary>
        /// 添加命令参数
        /// </summary>
        /// <param name="cmd">命令对象</param>
        /// <param name="cmdName">命令名</param>
        /// <param name="entity">实体对象</param>
        /// <returns>命令对象</returns>
        protected static IDbCommand AttachParams(IDbCommand cmd, string cmdName, T entity)
        {
            if (entity != null)
            {
                AttachParam(cmd, CmdColNames[cmdName], entity);
            }

            return cmd;
        }

        /// <summary>
        /// 执行从插入命令
        /// </summary>
        /// <param name="entity">实体</param>
        /// <param name="conn">使用的事务</param>
        /// <returns>影响的行数</returns>
        public static int Insert(T entity, IDbTransaction trans)
        {
            return DbHelper.ExecuteNonQuery(
                AttachParams(DbHelper.CreateCommand(CmdTexts["INSERT"]), "INSERT", entity), trans
            );
        }

        /// <summary>
        /// 执行从插入命令
        /// </summary>
        /// <param name="entity">实体</param>
        /// <returns>影响的行数</returns>
        public static int Insert(T entity)
        {
            return Insert(entity, null);
        }

        /// <summary>
        /// 执行从插入命令，并返回新记录的自动编号
        /// </summary>
        /// <param name="entity">实体</param>
        /// <param name="autoId">自动编号</param>
        /// <param name="trans">使用的事务</param>
        public static void Insert(T entity, out object autoId, IDbTransaction trans)
        {
            autoId = DbHelper.ExecuteScalar(
                AttachParams(
                    DbHelper.CreateCommand(CmdTexts["INSERT"]), "INSERT", entity
                ), trans
            );
        }

        /// <summary>
        /// 执行从插入命令，并返回新记录的自动编号
        /// </summary>
        /// <param name="entity">实体</param>
        /// <param name="autoId">自动编号</param>
        public static void Insert(T entity, out object autoId)
        {
            Insert(entity, out autoId, null);
        }

        /// <summary>
        /// 根据主键更新记录
        /// </summary>
        /// <param name="entity">实体对象</param>
        /// <param name="trans">使用的事务</param>
        /// <returns>影响的行数</returns>
        public static int Update(T entity, IDbTransaction trans)
        {
            return DbHelper.ExecuteNonQuery(
                AttachParams(DbHelper.CreateCommand(CmdTexts["UPDATE"]), "UPDATE", entity), trans);
        }

        /// <summary>
        /// 根据主键更新记录
        /// </summary>
        /// <param name="entity">实体对象</param>
        /// <returns>影响的行数</returns>
        public static int Update(T entity)
        {
            return Update(entity, null);
        }
        
        /// <summary>
        /// 根据主键删除记录
        /// </summary>
        /// <param name="entity">实体对象</param>
        /// <param name="trans">使用的事务</param>
        /// <returns>影响的行数</returns>
        public static int Delete(T entity, IDbTransaction trans)
        {
            return DbHelper.ExecuteNonQuery(
                AttachParams(DbHelper.CreateCommand(CmdTexts["DELETE"]), "DELETE", entity), trans
            );
        }

        /// <summary>
        /// 根据主键删除记录
        /// </summary>
        /// <param name="entity">实体对象</param>
        /// <returns>影响的行数</returns>
        public static int Delete(T entity)
        {
            return Delete(entity, null);
        }

        /// <summary>
        /// 把DataReader中的数据转换成实体对象列表
        /// </summary>
        /// <param name="reader">DataReader对象</param>
        /// <returns>实体对象列表</returns>
        public static List<T> DataReaderToEntityList(IDataReader reader)
        {
            List<T> list = new List<T>();

            while (reader.Read())
            {
                var entity = new T();
                
                foreach (var colAttr in ColAttrs)
                {
                    var pInfo = EntityType.GetProperty(colAttr.Value.EntityPropertyName);

                    try
                    {
                        pInfo.SetValue(entity,
                            Convert.ChangeType(reader[colAttr.Value.Name], pInfo.PropertyType), null);
                    }
                    catch
                    {
                    }
                }

                list.Add(entity);
            }

            reader.Close();

            return list;
        }

        /// <summary>
        /// 把DataReader中的数据转换成实体对象
        /// </summary>
        /// <param name="reader">DataReader对象</param>
        /// <returns>实体对象</returns>
        public static T DataReaderToEntity(IDataReader reader)
        {
            var list = DataReaderToEntityList(reader);
            return list.Count > 0 ? list[0] : default(T);
        }

        /// <summary>
        /// 根据主键读取单条记录并转换为实体对象
        /// </summary>
        /// <param name="entity">包含主键值的实体对象</param>
        /// <returns>实体对象</returns>
        public static T Read(T entity)
        {
            return DataReaderToEntity(DbHelper.ExecuteReader(
                AttachParams(DbHelper.CreateCommand(CmdTexts["READ"]), "READ", entity)
            ));
        }

        /// <summary>
        /// 读取表中的所有记录并转换为实体对象列表
        /// </summary>
        /// <returns>实体对象列表</returns>
        public static List<T> List()
        {
            return DataReaderToEntityList(
                DbHelper.ExecuteReader(DbHelper.CreateCommand(CmdTexts["LIST"]))
            );
        }
    }
}
